/**
 * @file
 * Abstract Syntax Notation One (ISO 8824, 8825) decoding
 *
 * @todo not optimised (yet), favor correctness over speed, favor speed over size
 */

/*
 * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * Author: Christiaan Simons <christiaan.simons@axon.tv>
 */

#include "lwip/opt.h"

#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */

#include "lwip/snmp_asn1.h"

/**
 * Retrieves type field from incoming pbuf chain.
 *
 * @param p points to a pbuf holding an ASN1 coded type field
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded type field
 * @param type return ASN1 type
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 */
err_t
snmp_asn1_dec_type(struct pbuf *p, u16_t ofs, u8_t *type)
{
    u16_t plen, base;
    u8_t *msg_ptr;

    plen = 0;
    while (p != NULL)
    {
        base = plen;
        plen += p->len;
        if (ofs < plen)
        {
            msg_ptr = p->payload;
            msg_ptr += ofs - base;
            *type = *msg_ptr;
            return ERR_OK;
        }
        p = p->next;
    }
    /* p == NULL, ofs >= plen */
    return ERR_ARG;
}

/**
 * Decodes length field from incoming pbuf chain into host length.
 *
 * @param p points to a pbuf holding an ASN1 coded length
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded length
 * @param octets_used returns number of octets used by the length code
 * @param length return host order length, upto 64k
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 */
err_t
snmp_asn1_dec_length(struct pbuf *p, u16_t ofs, u8_t *octets_used, u16_t *length)
{
    u16_t plen, base;
    u8_t *msg_ptr;

    plen = 0;
    while (p != NULL)
    {
        base = plen;
        plen += p->len;
        if (ofs < plen)
        {
            msg_ptr = p->payload;
            msg_ptr += ofs - base;

            if (*msg_ptr < 0x80)
            {
                /* primitive definite length format */
                *octets_used = 1;
                *length = *msg_ptr;
                return ERR_OK;
            }
            else if (*msg_ptr == 0x80)
            {
                /* constructed indefinite length format, termination with two zero octets */
                u8_t zeros;
                u8_t i;

                *length = 0;
                zeros = 0;
                while (zeros != 2)
                {
                    i = 2;
                    while (i > 0)
                    {
                        i--;
                        (*length) += 1;
                        ofs += 1;
                        if (ofs >= plen)
                        {
                            /* next octet in next pbuf */
                            p = p->next;
                            if (p == NULL)
                            {
                                return ERR_ARG;
                            }
                            msg_ptr = p->payload;
                            plen += p->len;
                        }
                        else
                        {
                            /* next octet in same pbuf */
                            msg_ptr++;
                        }
                        if (*msg_ptr == 0)
                        {
                            zeros++;
                            if (zeros == 2)
                            {
                                /* stop while (i > 0) */
                                i = 0;
                            }
                        }
                        else
                        {
                            zeros = 0;
                        }
                    }
                }
                *octets_used = 1;
                return ERR_OK;
            }
            else if (*msg_ptr == 0x81)
            {
                /* constructed definite length format, one octet */
                ofs += 1;
                if (ofs >= plen)
                {
                    /* next octet in next pbuf */
                    p = p->next;
                    if (p == NULL)
                    {
                        return ERR_ARG;
                    }
                    msg_ptr = p->payload;
                }
                else
                {
                    /* next octet in same pbuf */
                    msg_ptr++;
                }
                *length = *msg_ptr;
                *octets_used = 2;
                return ERR_OK;
            }
            else if (*msg_ptr == 0x82)
            {
                u8_t i;

                /* constructed definite length format, two octets */
                i = 2;
                while (i > 0)
                {
                    i--;
                    ofs += 1;
                    if (ofs >= plen)
                    {
                        /* next octet in next pbuf */
                        p = p->next;
                        if (p == NULL)
                        {
                            return ERR_ARG;
                        }
                        msg_ptr = p->payload;
                        plen += p->len;
                    }
                    else
                    {
                        /* next octet in same pbuf */
                        msg_ptr++;
                    }
                    if (i == 0)
                    {
                        /* least significant length octet */
                        *length |= *msg_ptr;
                    }
                    else
                    {
                        /* most significant length octet */
                        *length = (*msg_ptr) << 8;
                    }
                }
                *octets_used = 3;
                return ERR_OK;
            }
            else
            {
                /* constructed definite length format 3..127 octets, this is too big (>64k) */
                /**  @todo: do we need to accept inefficient codings with many leading zero's? */
                *octets_used = 1 + ((*msg_ptr) & 0x7f);
                return ERR_ARG;
            }
        }
        p = p->next;
    }

    /* p == NULL, ofs >= plen */
    return ERR_ARG;
}

/**
 * Decodes positive integer (counter, gauge, timeticks) into u32_t.
 *
 * @param p points to a pbuf holding an ASN1 coded integer
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
 * @param len length of the coded integer field
 * @param value return host order integer
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 *
 * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
 * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
 * of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
 */
err_t
snmp_asn1_dec_u32t(struct pbuf *p, u16_t ofs, u16_t len, u32_t *value)
{
    u16_t plen, base;
    u8_t *msg_ptr;

    plen = 0;
    while (p != NULL)
    {
        base = plen;
        plen += p->len;
        if (ofs < plen)
        {
            msg_ptr = p->payload;
            msg_ptr += ofs - base;
            if ((len > 0) && (len < 6))
            {
                /* start from zero */
                *value = 0;
                if (*msg_ptr & 0x80)
                {
                    /* negative, expecting zero sign bit! */
                    return ERR_ARG;
                }
                else
                {
                    /* positive */
                    if ((len > 1) && (*msg_ptr == 0))
                    {
                        /* skip leading "sign byte" octet 0x00 */
                        len--;
                        ofs += 1;
                        if (ofs >= plen)
                        {
                            /* next octet in next pbuf */
                            p = p->next;
                            if (p == NULL)
                            {
                                return ERR_ARG;
                            }
                            msg_ptr = p->payload;
                            plen += p->len;
                        }
                        else
                        {
                            /* next octet in same pbuf */
                            msg_ptr++;
                        }
                    }
                }
                /* OR octets with value */
                while (len > 1)
                {
                    len--;
                    *value |= *msg_ptr;
                    *value <<= 8;
                    ofs += 1;
                    if (ofs >= plen)
                    {
                        /* next octet in next pbuf */
                        p = p->next;
                        if (p == NULL)
                        {
                            return ERR_ARG;
                        }
                        msg_ptr = p->payload;
                        plen += p->len;
                    }
                    else
                    {
                        /* next octet in same pbuf */
                        msg_ptr++;
                    }
                }
                *value |= *msg_ptr;
                return ERR_OK;
            }
            else
            {
                return ERR_ARG;
            }
        }
        p = p->next;
    }
    /* p == NULL, ofs >= plen */
    return ERR_ARG;
}

/**
 * Decodes integer into s32_t.
 *
 * @param p points to a pbuf holding an ASN1 coded integer
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
 * @param len length of the coded integer field
 * @param value return host order integer
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 *
 * @note ASN coded integers are _always_ signed!
 */
err_t
snmp_asn1_dec_s32t(struct pbuf *p, u16_t ofs, u16_t len, s32_t *value)
{
    u16_t plen, base;
    u8_t *msg_ptr;

#if BYTE_ORDER == LITTLE_ENDIAN
    u8_t *lsb_ptr = (u8_t*)value;
#endif
#if BYTE_ORDER == BIG_ENDIAN
    u8_t *lsb_ptr = (u8_t*)value + sizeof(s32_t) - 1;
#endif
    u8_t sign;

    plen = 0;
    while (p != NULL)
    {
        base = plen;
        plen += p->len;
        if (ofs < plen)
        {
            msg_ptr = p->payload;
            msg_ptr += ofs - base;
            if ((len > 0) && (len < 5))
            {
                if (*msg_ptr & 0x80)
                {
                    /* negative, start from -1 */
                    *value = -1;
                    sign = 1;
                }
                else
                {
                    /* positive, start from 0 */
                    *value = 0;
                    sign = 0;
                }
                /* OR/AND octets with value */
                while (len > 1)
                {
                    len--;
                    if (sign)
                    {
                        *lsb_ptr &= *msg_ptr;
                        *value <<= 8;
                        *lsb_ptr |= 255;
                    }
                    else
                    {
                        *lsb_ptr |= *msg_ptr;
                        *value <<= 8;
                    }
                    ofs += 1;
                    if (ofs >= plen)
                    {
                        /* next octet in next pbuf */
                        p = p->next;
                        if (p == NULL)
                        {
                            return ERR_ARG;
                        }
                        msg_ptr = p->payload;
                        plen += p->len;
                    }
                    else
                    {
                        /* next octet in same pbuf */
                        msg_ptr++;
                    }
                }
                if (sign)
                {
                    *lsb_ptr &= *msg_ptr;
                }
                else
                {
                    *lsb_ptr |= *msg_ptr;
                }
                return ERR_OK;
            }
            else
            {
                return ERR_ARG;
            }
        }
        p = p->next;
    }
    /* p == NULL, ofs >= plen */
    return ERR_ARG;
}

/**
 * Decodes object identifier from incoming message into array of s32_t.
 *
 * @param p points to a pbuf holding an ASN1 coded object identifier
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded object identifier
 * @param len length of the coded object identifier
 * @param oid return object identifier struct
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 */
err_t
snmp_asn1_dec_oid(struct pbuf *p, u16_t ofs, u16_t len, struct snmp_obj_id *oid)
{
    u16_t plen, base;
    u8_t *msg_ptr;
    s32_t *oid_ptr;

    plen = 0;
    while (p != NULL)
    {
        base = plen;
        plen += p->len;
        if (ofs < plen)
        {
            msg_ptr = p->payload;
            msg_ptr += ofs - base;

            oid->len = 0;
            oid_ptr = &oid->id[0];
            if (len > 0)
            {
                /* first compressed octet */
                if (*msg_ptr == 0x2B)
                {
                    /* (most) common case 1.3 (iso.org) */
                    *oid_ptr = 1;
                    oid_ptr++;
                    *oid_ptr = 3;
                    oid_ptr++;
                }
                else if (*msg_ptr < 40)
                {
                    *oid_ptr = 0;
                    oid_ptr++;
                    *oid_ptr = *msg_ptr;
                    oid_ptr++;
                }
                else if (*msg_ptr < 80)
                {
                    *oid_ptr = 1;
                    oid_ptr++;
                    *oid_ptr = (*msg_ptr) - 40;
                    oid_ptr++;
                }
                else
                {
                    *oid_ptr = 2;
                    oid_ptr++;
                    *oid_ptr = (*msg_ptr) - 80;
                    oid_ptr++;
                }
                oid->len = 2;
            }
            else
            {
                /* accepting zero length identifiers e.g. for
                   getnext operation. uncommon but valid */
                return ERR_OK;
            }
            len--;
            if (len > 0)
            {
                ofs += 1;
                if (ofs >= plen)
                {
                    /* next octet in next pbuf */
                    p = p->next;
                    if (p == NULL)
                    {
                        return ERR_ARG;
                    }
                    msg_ptr = p->payload;
                    plen += p->len;
                }
                else
                {
                    /* next octet in same pbuf */
                    msg_ptr++;
                }
            }
            while ((len > 0) && (oid->len < LWIP_SNMP_OBJ_ID_LEN))
            {
                /* sub-identifier uses multiple octets */
                if (*msg_ptr & 0x80)
                {
                    s32_t sub_id = 0;

                    while ((*msg_ptr & 0x80) && (len > 1))
                    {
                        len--;
                        sub_id = (sub_id << 7) + (*msg_ptr & ~0x80);
                        ofs += 1;
                        if (ofs >= plen)
                        {
                            /* next octet in next pbuf */
                            p = p->next;
                            if (p == NULL)
                            {
                                return ERR_ARG;
                            }
                            msg_ptr = p->payload;
                            plen += p->len;
                        }
                        else
                        {
                            /* next octet in same pbuf */
                            msg_ptr++;
                        }
                    }
                    if (!(*msg_ptr & 0x80) && (len > 0))
                    {
                        /* last octet sub-identifier */
                        len--;
                        sub_id = (sub_id << 7) + *msg_ptr;
                        *oid_ptr = sub_id;
                    }
                }
                else
                {
                    /* !(*msg_ptr & 0x80) sub-identifier uses single octet */
                    len--;
                    *oid_ptr = *msg_ptr;
                }
                if (len > 0)
                {
                    /* remaining oid bytes available ... */
                    ofs += 1;
                    if (ofs >= plen)
                    {
                        /* next octet in next pbuf */
                        p = p->next;
                        if (p == NULL)
                        {
                            return ERR_ARG;
                        }
                        msg_ptr = p->payload;
                        plen += p->len;
                    }
                    else
                    {
                        /* next octet in same pbuf */
                        msg_ptr++;
                    }
                }
                oid_ptr++;
                oid->len++;
            }
            if (len == 0)
            {
                /* len == 0, end of oid */
                return ERR_OK;
            }
            else
            {
                /* len > 0, oid->len == LWIP_SNMP_OBJ_ID_LEN or malformed encoding */
                return ERR_ARG;
            }

        }
        p = p->next;
    }
    /* p == NULL, ofs >= plen */
    return ERR_ARG;
}

/**
 * Decodes (copies) raw data (ip-addresses, octet strings, opaque encoding)
 * from incoming message into array.
 *
 * @param p points to a pbuf holding an ASN1 coded raw data
 * @param ofs points to the offset within the pbuf chain of the ASN1 coded raw data
 * @param len length of the coded raw data (zero is valid, e.g. empty string!)
 * @param raw_len length of the raw return value
 * @param raw return raw bytes
 * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
 */
err_t
snmp_asn1_dec_raw(struct pbuf *p, u16_t ofs, u16_t len, u16_t raw_len, u8_t *raw)
{
    u16_t plen, base;
    u8_t *msg_ptr;

    if (len > 0)
    {
        plen = 0;
        while (p != NULL)
        {
            base = plen;
            plen += p->len;
            if (ofs < plen)
            {
                msg_ptr = p->payload;
                msg_ptr += ofs - base;
                if (raw_len >= len)
                {
                    while (len > 1)
                    {
                        /* copy len - 1 octets */
                        len--;
                        *raw = *msg_ptr;
                        raw++;
                        ofs += 1;
                        if (ofs >= plen)
                        {
                            /* next octet in next pbuf */
                            p = p->next;
                            if (p == NULL)
                            {
                                return ERR_ARG;
                            }
                            msg_ptr = p->payload;
                            plen += p->len;
                        }
                        else
                        {
                            /* next octet in same pbuf */
                            msg_ptr++;
                        }
                    }
                    /* copy last octet */
                    *raw = *msg_ptr;
                    return ERR_OK;
                }
                else
                {
                    /* raw_len < len, not enough dst space */
                    return ERR_ARG;
                }
            }
            p = p->next;
        }
        /* p == NULL, ofs >= plen */
        return ERR_ARG;
    }
    else
    {
        /* len == 0, empty string */
        return ERR_OK;
    }
}

#endif /* LWIP_SNMP */
